package com.platform.modules.acme.service.impl;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.platform.common.exception.BusinessException;
import com.platform.common.utils.Constant;
import com.platform.common.utils.Query;
import com.platform.config.AcmeProperties;
import com.platform.modules.acme.component.AcmeCache;
import com.platform.modules.acme.component.LetsEncrypt;
import com.platform.modules.acme.dao.AcmeCertificateDao;
import com.platform.modules.acme.dao.AcmeMemberDao;
import com.platform.modules.acme.dao.AcmeOrderDao;
import com.platform.modules.acme.entity.AcmeCertificateEntity;
import com.platform.modules.acme.entity.AcmeOrderEntity;
import com.platform.modules.acme.service.AcmeOrderService;
import com.platform.modules.acme.utils.AcmeUtil;
import com.platform.modules.acme.utils.FileUtils;
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest;
import org.shredzone.acme4j.*;
import org.shredzone.acme4j.challenge.Challenge;
import org.shredzone.acme4j.challenge.Dns01Challenge;
import org.shredzone.acme4j.challenge.Http01Challenge;
import org.shredzone.acme4j.challenge.TlsAlpn01Challenge;
import org.shredzone.acme4j.exception.AcmeException;
import org.shredzone.acme4j.toolbox.AcmeUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Base64Utils;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.URL;
import java.security.cert.X509Certificate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;


@Service
public class LetsEncryptOrderServiceImpl extends ServiceImpl<AcmeOrderDao, AcmeOrderEntity> implements AcmeOrderService {


    static Logger logger = LoggerFactory.getLogger(LetsEncryptOrderServiceImpl.class);

    @Autowired
    LetsEncrypt letsEncrypt;

    @Autowired
    AcmeProperties acme;

    @Autowired
    AcmeCache acmeCache;

    @Autowired
    AcmeMemberDao acmeMemberDao;

    @Autowired
    AcmeCertificateDao acmeCertificateDao;


    @Override
    public Order createOrder(Set<String> domainNames) {
        return null;
    }

    @Transactional
    @Override
    public Map<String, Object> getVerifyInfo(Set<String> domainNames, String challenge, String memberId, String csrData) {
        Map<String, Object> retMap = new HashMap<>();
        try {
            Account account = letsEncrypt.createLogin().getAccount();

            if (account == null) {
                logger.error("create account fail");
                throw new BusinessException("Time out to connect server,please try again!");
            }
            StringBuffer temp = new StringBuffer("");
            domainNames.forEach(domain -> {
                temp.append(domain).append(",");
            });
            String saveDomainName = temp.substring(0, temp.lastIndexOf(","));
            AcmeOrderEntity orderEntity = new AcmeOrderEntity();
            //每次进来都是新订单
            Order letsEncriptOrder = account.newOrder().domains(domainNames).create();
            orderEntity.setCreateTime(LocalDateTime.now());
            orderEntity.setExpireTime(LocalDateTime.ofInstant(letsEncriptOrder.getExpires(), ZoneId.systemDefault()));
            orderEntity.setStatusOnServer(letsEncriptOrder.getStatus().name());
            orderEntity.setDomainName(saveDomainName);
            orderEntity.setOrderCode(acmeCache.getOrderCode());
            orderEntity.setOrderLocation(letsEncriptOrder.getLocation().toString());
            orderEntity.setMemberId(memberId);
            orderEntity.setProdSource(Constant.ACME_PRODUCT_LETSENCRIPT);
            orderEntity.setAuthType(challenge);

            baseMapper.insert(orderEntity);

            AcmeCertificateEntity cert = new AcmeCertificateEntity();
            cert.setCsrData(csrData);
            cert.setCreateTime(LocalDateTime.now());
            cert.setOrderId(orderEntity.getOrderId());
            acmeCertificateDao.insert(cert);
            List<Authorization> authorizations = letsEncriptOrder.getAuthorizations();
            JSONArray verifyInfos = new JSONArray();

            for (Authorization auth : authorizations) {
                JSONObject verifyInfo = new JSONObject();
                if (auth.getStatus() == Status.VALID) {
                    //TODO 验证成功存验证信息?是否需要存验证信息？还是每次都去查？
                    //has  been verified
                    verifyInfo.put("valid", "true");
                    verifyInfos.add(verifyInfo);
                } else {
                    switch (challenge) {
                        case Http01Challenge.TYPE:
                            Http01Challenge http01 = auth.findChallenge(Http01Challenge.TYPE);
                            //储存验证文件，提供下载
                            //save file on storage ,provide to download
                            FileUtils.saveFile(http01.getAuthorization().getBytes(), acme.getVerifyFilePath(), http01.getToken(), false);
                            verifyInfo.put("valid", "false");
                            verifyInfo.put("content", http01.getAuthorization());
                            verifyInfo.put("fileName", http01.getToken());
                            verifyInfo.put("domain", auth.getIdentifier().getDomain());
                            break;
                        case Dns01Challenge.TYPE:
                            Dns01Challenge dns = auth.findChallenge(Dns01Challenge.TYPE);
                            verifyInfo.put("content", dns.getDigest());
                            verifyInfo.put("domain", auth.getIdentifier().getDomain());
                            verifyInfo.put("record", "_acme-challenge");
                            verifyInfo.put("valid", "false");
                            break;
                        case TlsAlpn01Challenge.TYPE:
                            auth.findChallenge(TlsAlpn01Challenge.TYPE);
                            break;
                        default:
                            break;
                    }
                    verifyInfos.add(verifyInfo);
                }
            }
            retMap.put("verifyInfo", verifyInfos);
            retMap.put("orderId", orderEntity.getOrderId());
        } catch (AcmeException ex) {
            logger.error(ex.getMessage());
            throw new BusinessException(ex.getMessage());
        } catch (IOException e) {
            logger.error("Can't write file.Error msg {}", e.getMessage());
            throw new BusinessException(e.getMessage());
        }
        return retMap;
    }

    @Override
    public Map<String, Object> verifyAuth(String orderId) {
        Map<String, Object> retMap = new HashMap<>();
        AcmeOrderEntity orderEntity = baseMapper.selectOrderById(orderId);

        Order order = null;
        try {
            if (orderEntity == null) {
                //can't find this order
                retMap.put("msg", "can not find this order");
                retMap.put("success", "false");
                return retMap;
            }
            if (orderEntity.getExpireTime().isBefore(LocalDateTime.now())) {
                //has been expired
                retMap.put("msg", "order has been expired");
                retMap.put("success", "false");
                return retMap;
            }
            Login login = letsEncrypt.createLogin();
            URL orderUrl = new URL(orderEntity.getOrderLocation());
            order = login.bindOrder(orderUrl);
            List<Authorization> list = order.getAuthorizations();
            for (Authorization auth : list) {

                //trigger to verify domain

                Challenge challenge = auth.findChallenge(orderEntity.getAuthType());

                if (challenge.getStatus() == Status.INVALID) {
                    //此类型订单不能后续绑定
                    orderEntity.setStatusOnServer(Status.INVALID.toString());
                    baseMapper.updateById(orderEntity);
                    retMap.put("msg", challenge.getError().getDetail() + "。请检查CAA记录是否允许该CA签发。");
                    retMap.put("success", "false");
                    return retMap;
                }
                //触发服务端验证
                challenge.trigger();
                Thread.sleep(3000L);
                //更新数据回来
                challenge.update();
                if (challenge.getStatus() == Status.VALID) {
                    auth.update();
                    orderEntity.setStatusOnServer(Status.VALID.toString());
                    baseMapper.updateById(orderEntity);
                    JcaPKCS10CertificationRequest letsEncryptCSR = AcmeUtil.getLetsEncryptCSR(orderEntity.getAcmeCertificateEntity().getCsrData());
                    if (order.getStatus() != Status.VALID) {
                        //执行下单操作
                        order.execute(letsEncryptCSR.getEncoded());
                    }
                } else {
                    orderEntity.setStatusOnServer(challenge.getStatus().toString());
                    baseMapper.updateById(orderEntity);
                    logger.error(auth.getIdentifier().getDomain() + " has not verify!");
                    String msg = "DNS".equalsIgnoreCase(auth.getIdentifier().getType()) ? " Please check your DNS record" : " Please check your website for http file";
                    retMap.put("msg", auth.getIdentifier().getDomain() + msg);
                    retMap.put("success", "false");
                    return retMap;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("verify domain error!order location is :{}", orderEntity.getOrderLocation());
            retMap.put("msg", "inter error!");
            retMap.put("success", "false");
            return retMap;
        }
        retMap.put("msg", "verify successful!");
        retMap.put("success", "true");
        return retMap;
    }

    @Override
    public void getCertificate(String orderId) {
        AcmeOrderEntity orderEntity = baseMapper.selectOrderById(orderId);
        try {
            Order order = letsEncrypt.createLogin().bindOrder(new URL(orderEntity.getOrderLocation()));
            if (order.getStatus() != Status.VALID) {
                JcaPKCS10CertificationRequest letsEncryptCSR = AcmeUtil.getLetsEncryptCSR(orderEntity.getAcmeCertificateEntity().getCsrData());
                order.execute(letsEncryptCSR.getEncoded());
                Thread.sleep(3000L);
                order.update();
            }
//        wait for a few second and update order

            if (order.getStatus() == Status.VALID) {
                X509Certificate x509Cert = order.getCertificate().getCertificate();
                byte[] encoded = x509Cert.getEncoded();
                String baseCert = Base64Utils.encodeToString(encoded);
                AcmeCertificateEntity certEntity = orderEntity.getAcmeCertificateEntity();
                certEntity.setCertData(baseCert);
                certEntity.setSerialNumber(x509Cert.getSerialNumber().toString());
                acmeCertificateDao.updateById(certEntity);
                orderEntity.setStatusOnServer(order.getStatus().toString());

                baseMapper.updateById(orderEntity);
                String[] domainNames = orderEntity.getDomainName().split(",");
                String domain = Arrays.stream(domainNames).findFirst().orElseThrow(Exception::new);
                if (domain.startsWith("*.")) {
                    domain = domain.replaceFirst("\\*", "_");
                }
                File savePathFolder = FileUtils.newFolder(acme.getClientFilePath() + File.separator + domain);
                String savePath = savePathFolder.getAbsolutePath() + File.separator;
                //certificate chain
                String fullchainPath = savePath + "fullchain.crt";
                try (FileWriter writer = new FileWriter(new File(fullchainPath))) {
                    order.getCertificate().writeCertificate(writer);
                }
                //certificate
                String filePath = savePath + domain + ".crt";
                try (FileWriter writer = new FileWriter(new File(filePath))) {
                    AcmeUtils.writeToPem(encoded, AcmeUtils.PemLabel.CERTIFICATE, writer);
                }
                FileUtils.zip(acme.getClientFilePath(), domain + ".zip", savePath);
                //删除文件夹
                FileUtils.deletefile(savePath);
            }
        } catch (Exception ex) {
            ex.printStackTrace();
            logger.error("get certificate error!");
        }
    }

    @Override
    public Page queryPage(Map<String, Object> params) {
        Page<AcmeOrderEntity> page = new Query<AcmeOrderEntity>(params).getPage();
        return (Page) baseMapper.selectPage(page, new QueryWrapper<AcmeOrderEntity>());
    }

}
